package org.lightrpc.server;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;

import javax.jws.WebService;

import org.lightrpc.LightRpcInitializationFailed;
import org.lightrpc.RemoteCallFailedException;
import org.lightrpc.Response;
import org.lightrpc.RpcCallParameters;
import org.lightrpc.RpcError;
import org.lightrpc.ServiceNameResolver;
import org.lightrpc.format.DataSerializer;
import org.lightrpc.format.json.GsonDataSerializer;

/**
 * Controls the flow of LightRPC.
 * 
 * @author miku
 * 
 */
public class ServerFlowEngine {

	private final ServiceNameResolver serviceNameResolver;

	private final DataSerializer dataSerializer;

	/**
	 * Constructor, where you give the class names, that act as webservices as a
	 * parameter. also needs a valid service name resolver and frontcontroller.
	 * 
	 * @param classnames
	 * @throws LightRpcInitializationFailed
	 */
	public ServerFlowEngine(String[] classnames,
			FrontController frontController,
			ServiceNameResolver serviceNameResolver)
			throws LightRpcInitializationFailed {
		initializeEngine(classnames);
		this.serviceNameResolver = serviceNameResolver;
		// TODO:when there are other formats, this should be done differently!
		dataSerializer = new GsonDataSerializer();
	}

	private void initializeEngine(String[] classnames)
			throws LightRpcInitializationFailed {
		for (String className : classnames) {
			try {
				Class<?> clazz = Class.forName(className);
				if (clazz.getAnnotation(WebService.class) == null &&
						clazz.getAnnotation(LightRpcWebService.class) == null) {
					throw new LightRpcInitializationFailed(
							"Could not find webservice annotation in class:"
									+ className);
				} else {
					WebServiceRegistry.registerService(clazz, clazz
							.newInstance());
				}
			} catch (ClassNotFoundException e) {
				throw new LightRpcInitializationFailed("Could not find class:"
						+ className + "\bwhen starting lightrpc engine", e);
			} catch (InstantiationException e) {
				throw new LightRpcInitializationFailed(
						"Could not instantiate class:"
								+ className
								+ "\nwhen starting lightrpc engine. Please check for empty constructors",
						e);
			} catch (IllegalAccessException e) {
				throw new LightRpcInitializationFailed(
						"Could not access class:"
								+ className
								+ "\nwhen starting lightrpc engine. Please check access to this class",
						e);
			}
		}
	}

	/**
	 * This method shouldn't return null, but a model object, that wraps a
	 * String/whatever object.
	 * 
	 * @param serviceName
	 * @param serviceFunctionName
	 * @param params
	 * @return
	 * @throws ServiceNotFoundException
	 */
	public Response incomingRpc(String serviceName, String serviceFunctionName,
			List<Object> params) throws ServiceNotFoundException,
			RemoteCallFailedException {
		Object service = serviceNameResolver.resolveClass(serviceName);

		try {
			RpcCallParameters paramCall = serviceNameResolver
					.resolveClassParameters(params, service.getClass());
			Method method = service.getClass().getMethod(serviceFunctionName,
					paramCall.getTypes());

			Object result = method.invoke(service, paramCall.getParams()
					.toArray());
			// wrap the result in a Remote Data object according to protocol in
			// use
			// the when more protocols, this should be hidden
			Response resp = new Response();
			dataSerializer.serialize(resp, result);
			return resp;
		} catch (SecurityException e) {
			// TODO:add logging
			e.printStackTrace();
			throw new RemoteCallFailedException(
					"Illegal operation for service:" + serviceName + " params:"
							+ params, e);
		} catch (NoSuchMethodException e) {
			// TODO:add logging
			e.printStackTrace();
			throw new RemoteCallFailedException(
					"No such service function for service:" + serviceName
							+ " params:" + params, e);
		} catch (IllegalArgumentException e) {
			// TODO:add logging
			e.printStackTrace();
			throw new RemoteCallFailedException(
					"Illegal arguments for service function:" + serviceName
							+ " params:" + params, e);
		} catch (IllegalAccessException e) {
			// TODO:add logging
			e.printStackTrace();
			throw new RemoteCallFailedException(
					"No access to this remote procedure. Check permissions for service:"
							+ serviceName + " params:" + params, e);
		} catch (InvocationTargetException e) {
			// This is a - ok, since the actual service threw the exception.
			// We'll just wrap it and forward it
			return formatException(e.getCause());
			// } catch (SerializationFailedException e) {
			// //TODO:add logging
			// TODO add more specific error message
			// logger.error("", e);
			// throw new RemoteCallFailedException("Could not serialize data:"
			// +serviceName+" params:"+params.values(), e);
		}
	}

	/**
	 * This is class not probably responsible for this operation.
	 * 
	 * @param t
	 * @return
	 */
	public Response formatException(Throwable t) {
		// wrap the java exception into a generic rpc error object, that can be
		// serialized
		Response resp = new Response();
		resp.setStatus(Response.RPC_STATUS_FAILURE);
		return dataSerializer.serialize(resp, new RpcError(t));

	}
}
